package sweet.web

import sweet.util._
import javax.servlet._
import javax.servlet.http._

/** This is the entry point of an Sweet web application. Declare this servlet and a servlet mapping
 * of this class to a web container web.xml file, and it will bootstrap a SweetWebApplication
 * object. All web requests will then need to dispatch through this servlet, and then the framework
 * will handle the request routing, controller dispatching and rendering of response.
 */
class SweetServlet extends GenericServlet with Logger {
  /** The main configuration object and setup codes for the SWEET web application */
  protected var sweetConfig : SweetConfig = null
  
  /** Get Servlet's init parameter in scala.Option style. */
  def initParameter(name : String): Option[String] = {
    val value = getInitParameter(name)
    if(value == null || value.trim.isEmpty)
      None       
    else
      Some(value)
  }
  
  /** Create new instance of SweetConfig object using a class string name. If the implementation provided
   * a constructor for ServletContext, it will use that constructor instead, else use default constructor. */
  def newSweetConfigInstance(clazz : Class[_]) : SweetConfig = {
    if(!classOf[SweetConfig].isAssignableFrom(clazz))
      throw new IllegalArgumentException(clazz +" must be type of sweet.web.SweetConfig.")
    
    //Find the constructor that has ServletContext parameter type
    val constructor = clazz.getConstructors.find{ c => 
      val params = c.getParameterTypes
      logger.debug("params(0) " + params(0))
      params.length > 1 && params(0).isAssignableFrom(classOf[ServletContext])
    }
    logger.debug("Sweet config constructor : " + constructor)
    val instance =  constructor match {
      case Some(const) =>
        val c = const.asInstanceOf[java.lang.reflect.Constructor[_]]
        c.newInstance(getServletContext).asInstanceOf[SweetConfig]
      case None => 
        val ins = clazz.newInstance.asInstanceOf[SweetConfig]
        //see if we can inject servletContext object in using setter method.
        clazz.getMethods.find{ m => m.getName == "servletContext_$eq" } match {
          case Some(m) => m.invoke(ins, getServletContext)
          case None => throw new Exception("Failed to setup sweetConfig with servletContext instance!")
        }
        ins
    }
    instance
  }
  
  override def init : Unit = {
    val configClassName = initParameter("sweet.configClass").getOrElse("sweet.web.DefaultSweetConfig")
    val sweetConfig = newSweetConfigInstance(Class.forName(configClassName))
    sweetConfig.init
  }
  
  override def destroy : Unit = {   
    sweetConfig.destroy
  }
  
  def service(sreq : ServletRequest, sres : ServletResponse) : Unit = {
    val req = sreq.asInstanceOf[HttpServletRequest]
    val res = sres.asInstanceOf[HttpServletResponse]
    
    val view = sweetConfig.requestProcessor.process(req, res)
    
    sweetConfig.viewEngine.render(view, req, res)
  }
}

trait InitDestroyable {
  def init : Unit
  def destroy : Unit
}

trait SweetConfig extends InitDestroyable {
  def servletContext : ServletContext
  def requestProcessor : RequestProcessor
  def viewEngine : ViewEngine
}

class DefaultSweetConfig(val servletContext : ServletContext) extends SweetConfig {
  val requestProcessor = new ControllerRequestProcessor
  val viewEngine = new JspViewEngine(servletContext)
  
  def init : Unit = {}
  def destroy : Unit = {}
}
    
trait RequestProcessor { 
  def process(req : HttpServletRequest, res : HttpServletResponse): View 
}
trait ViewEngine { 
  def render(view : View, req : HttpServletRequest, res : HttpServletResponse) : Unit 
}

sealed case class View(name : String, data : Map[String, Any])
case class EmptyView extends View("*EmptyView*", Map.empty)

object View {
  def apply() : View = { EmptyView() }
  def apply(name : String) : View = { new View(name, Map.empty) }
  def apply(name : String, pairs : Pair[String, Any]*) : View = { new View(name, Map.empty ++ pairs) }
}

class ControllerRequestProcessor extends RequestProcessor { 
  def process(req : HttpServletRequest, res : HttpServletResponse): View = { View() }
}
class JspViewEngine(servletContext : ServletContext) extends ViewEngine { 
  def render(view : View, req : HttpServletRequest, res : HttpServletResponse) : Unit  = {
    req.getRequestDispatcher(view.name).forward(req, res)
  }
}
class FreemarkerViewEngine(servletContext : ServletContext) extends ViewEngine { 
  def render(view : View, req : HttpServletRequest, res : HttpServletResponse) : Unit  = { 
    throw new Exception("Not yet implemented.")
  }
}

